package com.codi.bus.core.service.impl;

import com.codi.base.util.*;
import com.codi.bus.constant.GlobalConstant;
import com.codi.bus.constant.PayTypeConst;
import com.codi.bus.core.dao.*;
import com.codi.bus.core.domain.*;
import com.codi.bus.core.event.EventBusService;
import com.codi.bus.core.event.EventTypeDef;
import com.codi.bus.core.event.model.TradeEvent;
import com.codi.bus.core.fund.service.FundDividendChangeService;
import com.codi.bus.core.resultModel.*;
import com.codi.bus.core.service.*;
import com.codi.bus.core.service.model.*;
import com.codi.bus.core.service.util.HundsunUtils;
import com.codi.bus.core.service.util.ModelPopulateUtils;
import com.codi.bus.core.service.util.ObjectCreator;
import com.codi.bus.core.vo.*;
import com.codi.bus.util.NumberUtil;
import com.codi.fcloud.ufx.HsResponseUtil;
import com.codi.fcloud.ufx.constant.HSOutputParameterNames;
import com.codi.fcloud.ufx.constant.HsConst;
import com.codi.fcloud.ufx.hssale.query.AccountBankQuery;
import com.codi.fundData.domain.SecurityProduct;
import com.codi.fundData.service.SecurityProductService;
import com.codi.message.constant.ErrorConst;
import com.codi.message.constant.ErrorConstant;
import com.codi.message.util.MessageUtil;
import com.codi.trade.dto.*;
import com.codi.trade.response.HsAccoBankQueryResponse;
import com.codi.trade.service.UndoTradeApplyService;
import com.codi.trade.service.portfolio.HsCombRedeemTradeService;
import com.codi.trade.service.portfolio.HsCombSharesQryService;
import com.codi.trade.service.portfolio.HsPortfolioTradeService;
import com.codi.trade.service.portfolio.HsShareDetailQryService;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.*;

/**
 * 模块名
 *
 * @author spy
 * @version 1.0 2017-11-06 16:22
 * @since 1.0
 */
@Slf4j
@Service
@Transactional
@SuppressWarnings("Duplicates")
public class PortfolioServiceImpl implements PortfolioService {
    @Autowired
    private HsShareDetailQryService hsShareDetailQryService;

    @Autowired
    private HsPortfolioTradeService portfolioTradeService;

    @Autowired
    private UserService userService;

    @Autowired
    private QueryService queryService;

    @Autowired
    private TradeOrderDao tradeOrderDao;

    @Autowired
    private PortfolioFundOrderDao portfolioFundOrderDao;

    @Autowired
    private TransactionDao transactionDao;

    @Autowired
    private BankService bankService;

    @Autowired
    @Qualifier("userBankDao")
    private UserBankDao userBankDao;

    @Autowired
    @Qualifier("undoTradeApplyService")
    private UndoTradeApplyService undoTradeApplyService;

    @Autowired
    private FundCombinationService fundCombinationService;

    @Autowired
    private FundCombinationDao fundCombinationDao;

    @Autowired
    private SecurityProductService securityProductService;

    @Autowired
    private TradeService tradeService;

    @Autowired
    private BankLimitService bankLimitService;

    @Autowired
    private TradeOrderService tradeOrderService;

    @Autowired
    private HsCombRedeemTradeService hsCombRedeemTradeService;

    @Autowired
    private HsCombSharesQryService hsCombSharesQryService;

    @Autowired
    private AccountBankQuery accountBankQuery;

    @Autowired
    private EventBusService eventBusService;

    @Autowired
    private FundDividendChangeService fundDividendChangeService;

    @Autowired
    private MessageService messageService;
    @Autowired
    private LocaldataTradeOrderService localdataTradeOrderService;

    @Autowired
    private TradeOrdersDao tradeOrdersDao;


    private int MAX_THREAD_COUNT = Runtime.getRuntime().availableProcessors();


    @Override
    public BaseResult getLimits(Long userId, String portfolioCode, String capitalMode, String businCode) {
        BaseResult result = new BaseResult(false);

        User user = userService.getUser(userId);

        String custType = user.getIsOrg() ? GlobalConstant.CUST_TYPE_ORGANIZATION : GlobalConstant.CUST_TYPE_PERSONAL;

        List<PortfolioQryModel> portfolio = fundCombinationService.queryPorfolio(portfolioCode);
        if (ListUtil.isEmpty(portfolio)) {
            result.setErrorCode(ErrorConst.PORTFOLIO_IS_NOT_EXIST);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_IS_NOT_EXIST, portfolioCode));
            return result;
        }


        int fundCodesLength = portfolio.size();

        Map<String, String> fundCodeMap = new HashMap<>(fundCodesLength);
        //线程池
        ExecutorService exec = Executors.newFixedThreadPool(Math.min(fundCodesLength, MAX_THREAD_COUNT));

        //实例化CompletionService
        final CompletionService<TradeLimitListResult> completionService = new ExecutorCompletionService<>(exec);


        for (int i = 0; i < fundCodesLength; i++) {
            PortfolioQryModel model = portfolio.get(i);

            String fundCode = model.getFundCode();
            String shareType = model.getShareType();
            String precent = model.getInvestRate().toString();

            fundCodeMap.put(fundCode, precent);

            completionService.submit(() -> {
                TradeLimitListResult result1;

                result1 = queryService.queryTradeLimits(GlobalConstant.MAX_REQUEST_NUM, 1, 1, "1", fundCode, shareType,
                    capitalMode, businCode, custType);

                log.info("fundCode={},limits={}", fundCode, result1);

                return result1;
            });
        }


        List<TradeLimitListResult> limits = new ArrayList<>(fundCodesLength);
        try {
            for (int i = 0; i < fundCodesLength; i++) {

                Future<TradeLimitListResult> f = completionService.take();

                TradeLimitListResult limitListResult = f.get();

                if (!limitListResult.getSuccess()) {
                    result.setErrorCode(ErrorConst.COMMON_SYSTEM_ERROR);
                    result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.COMMON_SYSTEM_ERROR));
                    return result;
                }

                log.info("tradeLimitListResult={}", limitListResult);
                limits.add(limitListResult);
            }
        } catch (Exception e) {
            log.error("fail to get completionService future", e);

            result.setErrorCode(ErrorConst.COMMON_SYSTEM_ERROR);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.COMMON_SYSTEM_ERROR));
            return result;
        } finally {
            exec.shutdown();
        }

        TradeLimitModel tradeLimitModel = new TradeLimitModel();


        // 计算最大的限制
        // minValue 取最大的，maxValue取最小的, minHoldShares取最大
        BigDecimal minValue = null, maxValue = null, minHoldShares = null;
        for (int i = 0, j = limits.size(); i < j; i++) {
            TradeLimitListResult limitListResult = limits.get(i);

            if (ListUtil.isEmpty(limitListResult.getLimits())) {
                log.error("交易限制为null，请检查");
                // 生产肯定有值
                // result.setErrorCode(ErrorConst.COMMON_SYSTEM_ERROR);
                // result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.COMMON_SYSTEM_ERROR));
                // return result;
                continue;
            }

            TradeLimitModel item = limitListResult.getLimits().get(0);
            String precentStr = fundCodeMap.get(item.getFundCode());

            BigDecimal percent = new BigDecimal(precentStr);

            BigDecimal tempMinValue = item.getMinValue().divide(percent, 0, BigDecimal.ROUND_CEILING);
            if (minValue == null) {
                minValue = tempMinValue;
            } else {
                minValue = MoneyUtils.max(minValue, tempMinValue);
            }

            BigDecimal tempMaxValue = item.getMaxValue().divide(percent, 0, BigDecimal.ROUND_FLOOR);
            if (maxValue == null) {
                maxValue = tempMaxValue;
            } else {
                maxValue = MoneyUtils.min(maxValue, tempMaxValue);
            }
            BigDecimal tempMinHoldShares = item.getMinHoldShares().divide(percent, 0, BigDecimal.ROUND_CEILING);
            if (minHoldShares == null) {
                minHoldShares = tempMinHoldShares;
            } else {
                minHoldShares = MoneyUtils.max(minHoldShares, tempMaxValue);
            }
        }


        // 取整，最小值进位，最大值去尾
        if (minValue != null) {
            tradeLimitModel.setMinValue(minValue.setScale(0, BigDecimal.ROUND_CEILING));
        }
        if (maxValue != null) {
            tradeLimitModel.setMaxValue(maxValue.setScale(0, BigDecimal.ROUND_FLOOR));
        }
        if (minHoldShares != null) {
            tradeLimitModel.setMinHoldShares(minHoldShares.setScale(0, BigDecimal.ROUND_CEILING));
        }

        if (EqualsUtil.equals(businCode, GlobalConstant.FUND_BUSIN_CODE_APPLY_BUY)) {
            tradeLimitModel.setMinHoldShares(null);
        }

        // minValue> maxValue 就扯淡了
        //TODO


        //
        FundCombination fundCombination = fundCombinationDao.queryByCombinationCode(portfolioCode);

        if (fundCombination == null) {
            log.error("本地DB中基金组合信息不存在");
        } else {
            String minValueStr = fundCombination.getLimitMoneyPurchased();
            if (StringUtil.isNotEmpty(minValueStr)) {
                try {
                    if (tradeLimitModel.getMinValue() == null) {
                        tradeLimitModel.setMinValue(new BigDecimal(minValueStr));
                    } else {
                        minValue = MoneyUtils.max(tradeLimitModel.getMinValue(), new BigDecimal(minValueStr));
                        tradeLimitModel.setMinValue(minValue);
                    }
                } catch (Exception e) {
                    log.error("fail to set max min value {},{}", tradeLimitModel.getMinValue(), minValueStr);
                }
            }

        }

        result.setSuccess(true);
        result.setResult(tradeLimitModel);

        return result;
    }

    @Override
    public PortfolioSellLimitResult getSellLimits(PortfolioSellLimitRequestVo vo) {
        PortfolioSellLimitResult result = new PortfolioSellLimitResult();
        result.setSuccess(false);

        if (BigDecimal.ZERO.compareTo(vo.getTotalEnableShares()) >= 0) {
            result.setErrorCode(ErrorConst.PORTFOLIO_ENABLE_SHARES_IS_ZERO);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_ENABLE_SHARES_IS_ZERO));
            return result;
        }

        User user = userService.getUser(vo.getUserId());

        String custType = user.getIsOrg() ? GlobalConstant.CUST_TYPE_ORGANIZATION : GlobalConstant.CUST_TYPE_PERSONAL;

        String portfolioCode = vo.getPortfolioCode();
        String capitalMode = vo.getCaptialMode();

        List<PortfolioQryModel> portfolio = fundCombinationService.queryPorfolio(portfolioCode);
        if (ListUtil.isEmpty(portfolio)) {
            result.setErrorCode(ErrorConst.PORTFOLIO_IS_NOT_EXIST);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_IS_NOT_EXIST, portfolioCode));
            return result;
        }

        int fundCodesLength = portfolio.size();

        Map<String, PortfolioQryModel> fundMap = new HashMap<>(fundCodesLength);
        //线程池
        ExecutorService exec = Executors.newFixedThreadPool(Math.min(fundCodesLength, MAX_THREAD_COUNT));

        //实例化CompletionService
        final CompletionService<TradeLimitListResult> completionService = new ExecutorCompletionService<>(exec);


        for (int i = 0; i < fundCodesLength; i++) {
            PortfolioQryModel model = portfolio.get(i);

            String fundCode = model.getFundCode();
            String shareType = model.getShareType();
//            String precent = model.getInvestRate().toString();

            fundMap.put(fundCode, model);

            completionService.submit(() -> {
                TradeLimitListResult result1;

                result1 = queryService.queryTradeLimits(GlobalConstant.MAX_REQUEST_NUM,
                    1, 1, "1", fundCode, shareType,
                    capitalMode, GlobalConstant.FUND_BUSIN_CODE_APPLY_SELL, custType);

                log.info("fundCode={},limits={}", fundCode, result1);

                return result1;
            });
        }


        Map<String, TradeLimitModel> limitsMap = new HashMap<>(fundCodesLength);
        List<TradeLimitModel> limits = new ArrayList<>(fundCodesLength);
        try {
            for (int i = 0; i < fundCodesLength; i++) {

                Future<TradeLimitListResult> f = completionService.take();

                TradeLimitListResult limitListResult = f.get();

                if (!limitListResult.getSuccess()) {
                    result.setErrorCode(ErrorConst.COMMON_SYSTEM_ERROR);
                    result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.COMMON_SYSTEM_ERROR));
                    return result;
                }

                log.info("tradeLimitListResult={}", limitListResult);

                if (ListUtil.isNotEmpty(limitListResult.getLimits())) {
                    TradeLimitModel tradeLimitModel = limitListResult.getLimits().get(0);
                    limits.add(tradeLimitModel);
                    limitsMap.put(tradeLimitModel.getFundCode(), tradeLimitModel);
                }
            }
        } catch (Exception e) {
            log.error("fail to get completionService future", e);

            result.setErrorCode(ErrorConst.COMMON_SYSTEM_ERROR);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.COMMON_SYSTEM_ERROR));
            return result;
        } finally {
            exec.shutdown();
        }


        result.setLimits(limits);


        // 计算规则
        BigDecimal minValue = null;
        BigDecimal maxValue = null;

        BigDecimal minValueRate = null;
        BigDecimal maxValueRate = null;


        for (int i = 0, j = vo.getFunds().size(); i < j; i++) {
            PortfolioFundVo fund = vo.getFunds().get(i);

            if (fund.getEnableShares() == null) {
                log.error("fund code={} 基金可用份额为null", fund.getFundCode());
                continue;
            }

            if (BigDecimal.ZERO.compareTo(fund.getEnableShares()) >= 0) {
                log.warn("fund code={} 基金可用份额{}小于等于0", fund.getFundCode(), fund.getEnableShares());
                continue;
            }


            //
            TradeLimitModel tradeLimitModel = limitsMap.get(fund.getFundCode());
            if (tradeLimitModel != null && tradeLimitModel.getMinValue() != null
                && (tradeLimitModel.getMinValue().compareTo(BigDecimal.ONE) >= 0)) {

                BigDecimal tempMinVauleRate = tradeLimitModel.getMinValue()
                                                             .divide(fund.getEnableShares(), 2, BigDecimal.ROUND_CEILING);

                if (tempMinVauleRate.compareTo(BigDecimal.ONE) > 0) {
                    tempMinVauleRate = BigDecimal.ONE;
                }

                BigDecimal tempMinValue = tempMinVauleRate.multiply(vo.getTotalEnableShares());

                if (minValue == null) {
                    minValue = tempMinValue;
                    minValueRate = tempMinVauleRate;
                } else {
                    minValue = minValue.compareTo(tempMinValue) >= 0 ? minValue : tempMinValue;// get max
                    minValueRate = minValueRate.compareTo(tempMinVauleRate) >= 0 ? minValueRate : tempMinVauleRate;
                }
            }


            if (tradeLimitModel != null && tradeLimitModel.getMaxValue() != null) {
                //FIXME 最大就是1
                BigDecimal tempMaxValueRate = tradeLimitModel.getMaxValue()
                                                             .divide(fund.getEnableShares(), 2, BigDecimal.ROUND_CEILING);

                if (tempMaxValueRate.compareTo(BigDecimal.ONE) > 0) {
                    tempMaxValueRate = BigDecimal.ONE;
                }
                BigDecimal tempMaxValue = tempMaxValueRate.multiply(vo.getTotalEnableShares());
                if (maxValue == null) {
                    maxValue = tempMaxValue;
                    maxValueRate = tempMaxValueRate;
                } else {
                    maxValue = maxValue.compareTo(tempMaxValue) <= 0 ? maxValue : tempMaxValue;
                    maxValueRate = maxValueRate.compareTo(tempMaxValueRate) <= 0 ? maxValueRate : tempMaxValueRate;
                }
            }

            if (tradeLimitModel != null && tradeLimitModel.getMinHoldShares() != null
                && (tradeLimitModel.getMinHoldShares().compareTo(BigDecimal.ONE) >= 0)) {

                BigDecimal tempMinHoldSharesRate = (BigDecimal.ONE
                    .subtract(tradeLimitModel.getMinHoldShares()
                                             .divide(fund.getEnableShares(), 2, BigDecimal.ROUND_CEILING)));

                if (tempMinHoldSharesRate.compareTo(BigDecimal.ONE) > 0) {
                    tempMinHoldSharesRate = BigDecimal.ONE;
                }

                BigDecimal tempMinHoldShares = tempMinHoldSharesRate.multiply(vo.getTotalEnableShares());


                if (maxValue == null) {
                    maxValue = tempMinHoldShares;
                    maxValueRate = tempMinHoldSharesRate;
                } else {
                    maxValue = maxValue.compareTo(tempMinHoldShares) <= 0 ? maxValue : tempMinHoldShares;
                    maxValueRate = maxValueRate.compareTo(tempMinHoldSharesRate) <= 0 ? maxValueRate : tempMinHoldSharesRate;
                }
            }
        }

        if (minValue == null) {
            minValue = new BigDecimal("-1");
            minValueRate = new BigDecimal("-1");
        } else {
            minValue = minValue.setScale(2, RoundingMode.CEILING);
            minValueRate = minValueRate.setScale(2, RoundingMode.CEILING);
        }
        if (maxValue == null) {
            maxValue = new BigDecimal("-1");
            maxValueRate = new BigDecimal("-1");
        } else {
            maxValue = maxValue.setScale(2, RoundingMode.FLOOR);
            maxValueRate = maxValueRate.setScale(2, RoundingMode.FLOOR);
        }

        result.setMinValue(minValue);
        result.setMaxValue(maxValue);
        result.setMinValueRate(minValueRate);
        result.setMaxValueRate(maxValueRate);


        result.setSuccess(true);

        return result;
    }

    @Override
    public PortfolioTradeResult buy(SessionVo sessionVo, PortfolioTradeBuyVo vo) {
        PortfolioTradeResult result = new PortfolioTradeResult();
        result.setSuccess(false);

        // 验证组合是否存在
        List<PortfolioQryModel> fundList = fundCombinationService.queryPorfolio(vo.getPortfolioCode());
        if (ListUtil.isEmpty(fundList)) {
            result.setErrorCode(ErrorConst.PORTFOLIO_IS_NOT_EXIST);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_IS_NOT_EXIST, vo.getPortfolioCode()));
            return result;
        }

        String innerPortfolioCode = fundList.get(0).getInnerCombcode();

        Map<String, PortfolioQryModel> fundMap = new HashMap<>(fundList.size());

        for (PortfolioQryModel model : fundList) {
            fundMap.put(model.getFundCode(), model);
        }

        Long userId = sessionVo.getUserId();
        User user = userService.getUser(userId);
        String clientId = user.getExternalClientId();

        // 验证是否开户
        UserBank userBank = bankService.get(userId, vo.getTradeAcco());
        if (userBank == null) {
            result.setErrorCode(ErrorConst.TRADE_ACCOUNT_NOT_FUND);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.TRADE_ACCOUNT_NOT_FUND));
            return result;
        }

        String errorMsg;

        // 验证支付方式额度
        switch (vo.getPayType()) {

            case PayTypeConst.T0: //sell
                break;
            case PayTypeConst.OFFLINE:
                break;

            case PayTypeConst.BANK: //normal

                //银行卡限额校验
                BankLimit bankLimit = bankLimitService.selectByCodeAndChannel(userBank.getBankCode(), userBank.getCapitalMode());

                BigDecimal limit;

                if (bankLimit != null) {
                    // 单笔
                    if (bankLimit.getSingleLimit() == null || bankLimit.getSingleLimit() == -1) {
                    } else {
                        limit = BigDecimal.valueOf(bankLimit.getSingleLimit());
                        if (vo.getBalance().compareTo(limit) == 1) {
                            result.setErrorCode(ErrorConst.WALLET_HAS_REACH_MAX_SINGLE_DEAL);
                            errorMsg = MessageUtil.getErrorMsg(ErrorConst.WALLET_HAS_REACH_MAX_SINGLE_DEAL, limit);
                            result.setErrorMessage(errorMsg);
                            return result;
                        }
                    }

                    // 单日
                    if (bankLimit.getDayLimit() == null || bankLimit.getDayLimit() == -1) {
                    } else {
                        limit = BigDecimal.valueOf(bankLimit.getDayLimit());

                        BigDecimal daySum = tradeOrderService.getCurrentDayDealSum(vo.getTradeAcco());

                        BigDecimal total = MoneyUtils.add(daySum, vo.getBalance());

                        if (total.compareTo(limit) == 1) {
                            result.setErrorCode(ErrorConst.WALLET_HAS_REACH_MAX_DAY_DEAL);
                            errorMsg = MessageUtil.getErrorMsg(ErrorConst.OFFLINE_MIN_VALUE_LIMIT, limit);
                            result.setErrorMessage(errorMsg);
                            return result;
                        }
                    }

                    // 单月
                    if (bankLimit.getMonthLimit() == null || bankLimit.getMonthLimit() == -1) {
                    } else {
                        limit = BigDecimal.valueOf(bankLimit.getMonthLimit());

                        BigDecimal monthSum = tradeOrderService.getCurrentMonthDealSum(vo.getTradeAcco());

                        BigDecimal total = MoneyUtils.add(monthSum, vo.getBalance());

                        if (total.compareTo(limit) == 1) {
                            result.setErrorCode(ErrorConst.WALLET_HAS_REACH_MAX_MONTH_DEAL);
                            errorMsg = MessageUtil.getErrorMsg(ErrorConst.WALLET_HAS_REACH_MAX_MONTH_DEAL, limit);
                            result.setErrorMessage(errorMsg);
                            return result;
                        }
                    }
                }// end bankLimit != null
                break;
            default:

                result.setErrorCode(ErrorConst.COMMON_NOT_SUPPORT_PAY_TYPE);
                result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.COMMON_NOT_SUPPORT_PAY_TYPE));
                return result;
        }


        String content = new StringBuilder()
            .append(innerPortfolioCode)
            .append("|")
            .append(vo.getBalance().toString()).toString();

        PortfolioTradeDto tradeDto = PortfolioTradeDto.builder()
                                                      .trade_acco(vo.getTradeAcco())
                                                      .password(MD5.MD5Encode(vo.getTradePassword()))
                                                      .capital_mode(userBank.getCapitalMode())
//                                                      .portfolio_code(vo.getPortfolioCode())
                                                      .ext_combcode(vo.getPortfolioCode())
                                                      .communic_content(content)
                                                      .fund_busin_code(GlobalConstant.FUND_BUSIN_CODE_APPLY_BUY)
                                                      .invest_flag(HsConst.INVEST_FLAG_PLAN)
                                                      .detail_fund_way(HsConst.DETAIL_FUND_WAY)
                                                      .busin_ass_code(HsConst.BUSIN_ASS_CODE_PORTFOLIO)
                                                      .fund_interface_type(HsConst.FUND_INTERFACE_TYPE_QUICK_PAY)
                                                      .build();


        Map<String, Object> map = portfolioTradeService.makeTrade(tradeDto);


        Integer orderStatus = 0;
        String externalApplyNo = "";

        if (!HsResponseUtil.isOperationOk(map)) {
            String errorInfo = map.get(HSOutputParameterNames.ERROR_INFO).toString();
            result.setErrorCode(ErrorConstant.ERROR_HUNDSUNAPI_WRONG);
            result.setErrorMessage(errorInfo);
            // 订单状态
            orderStatus = GlobalConstant.ORDER_STATUS_APPLY_FAIL;
            log.error("fail to buy portfolio:{}", errorInfo);
        } else {
            result.setSuccess(true);

            Date now = new Date();
            // 订单状态
            orderStatus = GlobalConstant.ORDER_STATUS_APPLY;
            // 返回的申请编号
            externalApplyNo = map.get(HSOutputParameterNames.ALLOT_NO).toString();

            localdataTradeOrderService.addApplyOrder(userId.intValue(), externalApplyNo, 1, sessionVo.getChannelId(), vo.getTradeAcco(), clientId);

            result.setAllotNo(externalApplyNo);
            // 申请日期
            result.setApplyDate(DateUtils.formatDate(now));
            // 申请时间
            result.setApplyTime(map.get(HSOutputParameterNames.APPLY_TIME).toString());
            // 下单日期
            result.setOrderDate(DateUtils.dateToSimpleStr(now));
            // 下单时间
            result.setOrderTime(DateUtils.dateToTime(now));


            TradeEvent event = TradeEvent.builder()
                                         .userId(userId)
                                         .payType(vo.getPayType())
                                         .portfolioCode(vo.getPortfolioCode())
                                         .innerPortfolioCode(innerPortfolioCode)
                                         .balance(vo.getBalance())
                                         .allotNo(externalApplyNo)
                                         .count(0)
                                         .build();
            event.setEventType(EventTypeDef.TRADE_BUY_PORTFOLIO);

            eventBusService.postAsync(event);


            // 修改分红方式
//            FundDividendChange fundDividendChange = FundDividendChange.builder()
//                                                                      .userId(userId)
//                                                                      .portfolioCode(vo.getPortfolioCode())
//                                                                      .innerPortfolioCode(innerPortfolioCode)
//                                                                      .tradeAllotNo(externalApplyNo)
//                                                                      .build();
//
//            fundDividendChangeService.addPortfolioApplyWait(fundDividendChange);

        }

        // 订单流水号
        String orderSn = vo.getApplyNo() + userId.longValue();
        // 插入订单数据到本地数据库

        storeData(map, userBank.getCapitalMode(), userBank.getBankCode(), userBank.getUserBankNo(), vo.getBalance(),
            orderSn, userId, sessionVo.getChannelId(), GlobalConstant.TRADE_TYPE_BUY, orderStatus, new Date(),
            externalApplyNo, vo.getPortfolioCode(), tradeDto.getCommunic_content());


        return result;
    }

    @Override
    public PortfolioTradeResult sell38(SessionVo sessionVo, PortfolioTradeSellVo vo) {

        PortfolioTradeResult result = new PortfolioTradeResult();
        result.setSuccess(false);

        // 验证组合是否存在
//        List<PortfolioQryModel> fundList = fundCombinationService.queryPorfolio(vo.getPortfolioCode());
//        if (ListUtil.isEmpty(fundList)) {
//            result.setErrorCode(ErrorConst.PORTFOLIO_IS_NOT_EXIST);
//            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_IS_NOT_EXIST, vo.getPortfolioCode()));
//            return result;
//        }

        Long userId = sessionVo.getUserId();
        User user = userService.getUser(userId);

        // 验证是否开户
        HsAccoBankQueryDto accoBankQueryDto = HsAccoBankQueryDto.builder()
                                                                .trade_acco(vo.getPortfolioTradeAcco())
                                                                .client_id(user.getExternalClientId())
                                                                .build();

        List<HsAccoBankQueryResponse> hsBanks = accountBankQuery.query(accoBankQueryDto);

        if (ListUtil.isEmpty(hsBanks)) {
            log.error("此用户不存在该交易账号[{}]的银行卡", vo.getPortfolioTradeAcco());

            result.setErrorCode(ErrorConst.TRADE_ACCOUNT_NOT_FUND);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.TRADE_ACCOUNT_NOT_FUND));

            return result;
        }

        HsAccoBankQueryResponse hsAccoBankQueryResponse = hsBanks.get(0);

        String bankAccount = hsAccoBankQueryResponse.getBank_account();

        List<UserBank> userBanks = bankService.getBanks(userId, bankAccount);
        if (userBanks == null || userBanks.isEmpty()) {
            result.setErrorCode(ErrorConst.TRADE_ACCOUNT_NOT_FUND);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.TRADE_ACCOUNT_NOT_FUND));
            return result;
        }

        UserBank userBank = userBanks.get(0);

        // 可用总额
//        BigDecimal totalShares = queryAssertByNewTradeAcco(vo.getPortfolioTradeAcco(), vo.getInnerPortfolioCode());
//
//
//        if (totalShares.compareTo(BigDecimal.ZERO) == 0) {
//            result.setErrorCode(ErrorConst.PORTFOLIO_ENABLE_SHARES_IS_ZERO);
//            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_ENABLE_SHARES_IS_ZERO));
//            return result;
//        }
//
//        if (vo.getBalance().compareTo(totalShares) == 1) {
//            result.setErrorCode(ErrorConst.PORTFOLIO_OVER_ENABLE_SHARES);
//            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_OVER_ENABLE_SHARES));
//            return result;
//        }
//
//
//        String combRedeemRate = vo.getBalance().divide(totalShares, 4, RoundingMode.DOWN).toString();

        log.info("comb redeem rate={}", vo.getRedeemRate());

        String combRedeemRate = MoneyUtils.formatMoney(vo.getRedeemRate(), 2);

        BigDecimal redeemRate = new BigDecimal(combRedeemRate);
        if (redeemRate.compareTo(BigDecimal.ZERO) > 0 && redeemRate.compareTo(BigDecimal.ONE) <= 1) {

        } else {
            result.setErrorCode(ErrorConst.PORTFOLIO_COMB_REDEEM_RATE_ERROR);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_COMB_REDEEM_RATE_ERROR));
            return result;
        }

        log.info("redeemRate={}", combRedeemRate);

        HsCombRedeemTradeDto hsCombRedeemTradeDto = HsCombRedeemTradeDto.builder()
                                                                        .trade_acco(vo.getPortfolioTradeAcco())
                                                                        .password(MD5.MD5Encode(vo.getTradePassword()))
                                                                        .portfolio_code(vo.getInnerPortfolioCode())
                                                                        .comb_redeem_rate(combRedeemRate)
                                                                        .build();


        Map<String, Object> map = hsCombRedeemTradeService.redeem(hsCombRedeemTradeDto);


        Integer orderStatus = 0;
        String externalApplyNo = "";

        if (!HsResponseUtil.isOperationOk(map)) {
            String errorInfo = map.get(HSOutputParameterNames.ERROR_INFO).toString();
            result.setErrorCode(ErrorConstant.ERROR_HUNDSUNAPI_WRONG);
            result.setErrorMessage(errorInfo);
            // 订单状态
            orderStatus = GlobalConstant.ORDER_STATUS_APPLY_FAIL;
            log.error("portfolio sell38 - error info:{}", errorInfo);
        } else {
            result.setSuccess(true);

            Date now = new Date();

            String time = DateUtils.dateToTime(now);

            // 订单状态
            orderStatus = GlobalConstant.ORDER_STATUS_APPLY;
            // 返回的申请编号
            externalApplyNo = map.get(HSOutputParameterNames.ALLOT_NO).toString();

            localdataTradeOrderService.addApplyOrder(userId.intValue(), externalApplyNo, 1, sessionVo.getChannelId(), vo.getPortfolioTradeAcco(), user.getExternalClientId());

            result.setAllotNo(externalApplyNo);
            // 申请日期
            result.setApplyDate(DateUtils.formatDate(now));
            // 申请时间
            result.setApplyTime(time);
            // 下单日期
            result.setOrderDate(DateUtils.dateToSimpleStr(now));
            // 下单时间
            result.setOrderTime(time);


            TradeEvent event = TradeEvent.builder()
                                         .userId(userId)
                                         .innerPortfolioCode(vo.getInnerPortfolioCode())
                                         .allotNo(externalApplyNo)
                                         .build();
            event.setEventType(EventTypeDef.TRADE_SELL_PORTFOLIO);

            eventBusService.postAsync(event);
        }

        // 订单流水号
        String orderSn = vo.getApplyNo() + userId.longValue();
        // 插入订单数据到本地数据库

        storeData(map, userBank.getCapitalMode(), userBank.getBankCode(), bankAccount, BigDecimal.ZERO,
            orderSn, userId, sessionVo.getChannelId(), GlobalConstant.TRADE_TYPE_SELL, orderStatus, new Date(),
            externalApplyNo, vo.getInnerPortfolioCode(), null);

        return result;
    }

    @Override
    public BaseResult undo(SessionVo sessionVo, PortfolioTradeUndoVo vo) {
        BaseResult result = new BaseResult(false);

        Long userId = sessionVo.getUserId();

        PortfolioFundOrder order = portfolioFundOrderDao.getByApplyNo(vo.getPortfolioAllotNo());
        if (order == null) {
            result.setErrorCode(ErrorConst.TRADE_IS_NOT_EXIST);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.TRADE_IS_NOT_EXIST));
            return result;
        }
        if (!StringUtil.isEmpty(order.getExternalUndoNo())) {
            result.setErrorCode(ErrorConst.PORTFOLIO_TRADE_UNDO_IS_REPEAT);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_TRADE_UNDO_IS_REPEAT));
            return result;
        }

        TradeOrder tradeOrder = tradeOrderDao.getEntityByOrderId(order.getOrderId());
        if (tradeOrder == null) {
            result.setErrorCode(ErrorConstant.ERROR_NO_ORDER_FIND);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConstant.ERROR_NO_ORDER_FIND));
            return result;
        }

//        UserBank userBank = userBankDao.getEntityByBankNo(userId, tradeOrder.getBankAccount(),
//            tradeOrder.getPaymentType());
//        if (userBank == null || StringUtil.isEmpty(userBank.getExternalTradeAccount())) {
//            result.setErrorCode(ErrorConstant.ERROR_NO_BANK_CARD_FIND);
//            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConstant.ERROR_NO_BANK_CARD_FIND));
//            return result;
//        }

        User user = userService.getUser(userId);

        // 判断此交易是否可以撤单
        ApplyModel applyModel = queryService.queryPortfolioApply(vo.getPortfolioAllotNo());

        if (applyModel != null) {
            applyModel.setIsOrg(user.getIsOrg());

            if (!applyModel.getUndoStatus()) {
                result.setErrorType(GlobalConstant.ERROR_TYPE_BUSINESS);
                result.setErrorCode(ErrorConstant.ERROR_UNDO_DISABLE);
                return result;
            }
        }


        UndoTradeApplyDto undoDto = UndoTradeApplyDto.builder()
                                                     .portfolio_allot_no(vo.getPortfolioAllotNo())
                                                     .password(MD5.MD5Encode(vo.getTradePassword()))
                                                     .trade_acco(applyModel.getTradeAcco())
                                                     .trade_source(sessionVo.getChannelId())
                                                     .build();


        Map<String, Object> map = undoTradeApplyService.undo(undoDto);

        String externalUndoNo = "";
        if (!HsResponseUtil.isOperationOk(map)) {
            String errorInfo = map.get(HSOutputParameterNames.ERROR_INFO).toString();
            result.setSuccess(false);
            result.setErrorCode(ErrorConstant.ERROR_HUNDSUNAPI_WRONG);
            result.setErrorMessage(errorInfo);
            log.error("undo - error info:{}", errorInfo);
        } else {
            result.setSuccess(true);
            // 返回的申请编号
            externalUndoNo = map.get(HSOutputParameterNames.ALLOT_NO).toString();

            localdataTradeOrderService.addApplyOrder(userId.intValue(), externalUndoNo, 0, sessionVo.getChannelId(), applyModel.getTradeAcco(), user.getExternalClientId());
            TradeOrders tradeOrders = TradeOrders.builder().taconfirmFlag("4").portfolioAllotNo(vo.getPortfolioAllotNo()).build();
            tradeOrdersDao.updateByPortfolioApplyNo(tradeOrders);

            order.setExternalUndoNo(externalUndoNo);
            order.setStatus(GlobalConstant.FUND_STATUS_CANCEL);
            // 更新订单基金表

            PortfolioFundOrder record = new PortfolioFundOrder();
            record.setId(order.getId());
            record.setExternalUndoNo(externalUndoNo);
            record.setStatus(order.getStatus());
            record.setLastUpdatedDate(new Date());
            portfolioFundOrderDao.updateByPrimaryKeySelective(record);

            // 撤单
            tradeOrder.setOrderStatus(GlobalConstant.ORDER_STATUS_CANCEL);
            // 更新状态
            tradeOrderDao.updateStatus(tradeOrder);

            TradeEvent event = TradeEvent.builder()
                                         .userId(userId)
                                         .allotNo(externalUndoNo)
                                         .build();

            event.setEventType(EventTypeDef.TRADE_UNDO_PORTFOLIO);
            eventBusService.postAsync(event);
            try {
                messageService.updatePortfolioUndoUserMsg(userId,vo.getPortfolioAllotNo(),order.getPortfolioCode());

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 插入交易记录流水
        Transaction transaction = ObjectCreator.createTransaction(map);
        // 不管成功或失败，都插入交易流水
        transactionDao.add(transaction);

        return result;
    }

    @Override
    public NewsModel getNewsModel(String[] fundCodes) {

        //线程池
        ExecutorService executorService = Executors.newFixedThreadPool(Math.min(fundCodes.length, MAX_THREAD_COUNT));
        //实例化CompletionService
        final CompletionService<NewsModel> completionService = new ExecutorCompletionService<>(executorService);

        for (int i = 0; i < fundCodes.length; i++) {
            String fundCode = fundCodes[i];

            completionService.submit(() -> {
                TradeLimitListResult result = new TradeLimitListResult();

                NewsModel newsModel = queryService.getRiskLevel(fundCode);
                if (newsModel == null) {
                    log.warn("fail to query risk level[fundCode:{}]", fundCode);
                    result.setSuccess(false);
                    result.setErrorCode(ErrorConst.RISK_LEVEL_NOT_FUND);
                    result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.RISK_LEVEL_NOT_FUND));
                    return null;
                }

                log.info("fundCode={},limits={}", fundCode, result);

                return newsModel;
            });
        }

        List<NewsModel> list = new ArrayList<>(fundCodes.length);
        try {
            for (int i = 0; i < fundCodes.length; i++) {

                Future<NewsModel> f = completionService.take();

                NewsModel newsModel = f.get();
                log.info("get riskLevel={}", newsModel);
                list.add(newsModel);
            }
        } catch (Exception e) {
            log.error("fail to get news Model", e);
        } finally {
            executorService.shutdown();
        }

        Collections.sort(list, (o1, o2) -> {
            if (o1.getRiskLevel() > o2.getRiskLevel()) {
                return -1;
            } else if (o1.getRiskLevel() < o2.getRiskLevel()) {
                return 1;
            } else {
                return 0;
            }
        });

        log.info("max risk level is [{}]", list.get(0));

        return list.get(0);
    }

    @Override
    public PortfolioRate getApplyRate(Long userId, String fundCodesStr) {
        User user = userService.getUser(userId);
        String cust_type = user.getIsOrg() ? GlobalConstant.CUST_TYPE_ORGANIZATION : GlobalConstant.CUST_TYPE_PERSONAL;

        String[] fundCodesArray = fundCodesStr.split("\\|");

        int fundCodesLength = fundCodesArray.length / 2;

        //线程池
        ExecutorService executorService = Executors.newFixedThreadPool(Math.min(fundCodesLength, MAX_THREAD_COUNT));
        //实例化CompletionService
        final CompletionService<FundTradeRatio> completionService = new ExecutorCompletionService<>(executorService);


        for (int i = 0; i < fundCodesArray.length; i++) {
            String fundCode = fundCodesArray[i];
            BigDecimal balance = new BigDecimal(fundCodesArray[++i]);

            completionService.submit(() -> {
                FundTradeRatio rate = queryService.getApplyFundRate(fundCode, balance, cust_type);

                log.info("query[fundCode={},balance,custype={}] apply rate is {}", fundCode, balance, cust_type, rate);

                return rate;
            });
        }

        List<PortfolioSingleFundRate> singleFundRates = new ArrayList<>(fundCodesLength);

        BigDecimal finalFee = new BigDecimal(0); //最终费用
        BigDecimal originalFee = new BigDecimal(0); //原始的费用
        BigDecimal discountFee = new BigDecimal(0); //优惠金额
        try {
            for (int i = 0; i < fundCodesLength; i++) {

                Future<FundTradeRatio> f = completionService.take();

                FundTradeRatio fundTradeRatio = f.get();
                if (fundTradeRatio == null) {
                    log.debug("f.get()基金费率为null");
                    continue;
                }
                if (fundTradeRatio.getSuccess()) {
                    PortfolioSingleFundRate singleFundRate = new PortfolioSingleFundRate();
                    singleFundRate.setFundCode(fundTradeRatio.getFund_code());
                    singleFundRate.setFee(fundTradeRatio.getFee());
                    singleFundRate.setOriginFee(fundTradeRatio.getOrigin_fee());
                    singleFundRate.setDiscountFee(fundTradeRatio.getDiscount_fee());

                    singleFundRates.add(singleFundRate);

                    if (!Strings.isNullOrEmpty(fundTradeRatio.getFee())) {
                        finalFee = MoneyUtils.add(finalFee, new BigDecimal(NumberUtil.formatNumber(fundTradeRatio.getFee())));
                    }

                    if (!Strings.isNullOrEmpty(fundTradeRatio.getOrigin_fee())) {
                        originalFee = MoneyUtils.add(originalFee, new BigDecimal(NumberUtil.formatNumber(fundTradeRatio.getOrigin_fee())));
                    }

                    if (!Strings.isNullOrEmpty(fundTradeRatio.getDiscount_fee())) {
                        discountFee = MoneyUtils.add(discountFee, new BigDecimal(NumberUtil.formatNumber(fundTradeRatio.getDiscount_fee())));
                    }

                } else {
                    log.warn("fund rate is null");

                    if (!Strings.isNullOrEmpty(fundTradeRatio.getFund_code())) {
                        PortfolioSingleFundRate singleFundRate = new PortfolioSingleFundRate();
                        singleFundRate.setFundCode(fundTradeRatio.getFund_code());
                        singleFundRate.setFee("0");
                        singleFundRate.setOriginFee("0");
                        singleFundRate.setDiscountFee("0");
                        singleFundRates.add(singleFundRate);
                    }
                }

            }
        } catch (Exception e) {
            log.error("fail to get news Model", e);
        } finally {
            executorService.shutdown();
        }


        Map<String, PortfolioSingleFundRate> map = new HashMap<>();
        singleFundRates.forEach(item -> {
            map.put(item.getFundCode(), item);
        });

        List<PortfolioSingleFundRate> sortFundRates = new ArrayList<>(fundCodesLength);

        for (int i = 0; i < fundCodesArray.length; i++) {
            sortFundRates.add(map.get(fundCodesArray[i]));
            i++;
        }

        PortfolioRate rate = new PortfolioRate();
        rate.setFee(MoneyUtils.formatMoney(finalFee, 2));
        rate.setOriginFee(MoneyUtils.formatMoney(originalFee, 2));
        rate.setDiscountFee(MoneyUtils.formatMoney(discountFee, 2));
        rate.setPortfolio(sortFundRates);

        return rate;
    }

    @Override
    public PortfolioRate getRedeemRate(Long userId, String fundCodesStr) {
        User user = userService.getUser(userId);
        String cust_type = user.getIsOrg() ? GlobalConstant.CUST_TYPE_ORGANIZATION : GlobalConstant.CUST_TYPE_PERSONAL;

        String[] fundCodesArray = fundCodesStr.split("\\|");

        int fundCodesLength = fundCodesArray.length / 2;

        //线程池
        ExecutorService executorService = Executors.newFixedThreadPool(Math.min(fundCodesLength, MAX_THREAD_COUNT));
        //实例化CompletionService
        final CompletionService<FundTradeRatio> completionService = new ExecutorCompletionService<>(executorService);


        for (int i = 0; i < fundCodesArray.length; i++) {
            String fundCode = fundCodesArray[i];
            BigDecimal balance = new BigDecimal(fundCodesArray[++i]);

            completionService.submit(() -> {
                FundTradeRatio rate = queryService.getExceedFundRate(fundCode, balance, cust_type);

                log.info("query[fundCode={},balance,custype={}] redeem rate is {}", fundCode, balance, cust_type, rate);

                return rate;
            });
        }

        List<PortfolioSingleFundRate> singleFundRates = new ArrayList<>(fundCodesLength);

        BigDecimal finalFee = new BigDecimal(0); //最终费用
        BigDecimal originalFee = new BigDecimal(0); //原始的费用
        BigDecimal discountFee = new BigDecimal(0); //优惠金额
        try {
            for (int i = 0; i < fundCodesLength; i++) {

                Future<FundTradeRatio> f = completionService.take();

                FundTradeRatio fundTradeRatio = f.get();
                if (fundTradeRatio == null) {
                    log.debug("f.get()基金费率为null");
                    continue;
                }
                if (fundTradeRatio.getSuccess()) {

                    PortfolioSingleFundRate singleFundRate = new PortfolioSingleFundRate();
                    singleFundRate.setFundCode(fundTradeRatio.getFund_code());
                    singleFundRate.setFee(fundTradeRatio.getFee());
                    singleFundRate.setOriginFee(fundTradeRatio.getOrigin_fee());
                    singleFundRate.setDiscountFee(fundTradeRatio.getDiscount_fee());

                    singleFundRates.add(singleFundRate);

                    finalFee = MoneyUtils.add(finalFee, new BigDecimal(fundTradeRatio.getFee()));
                    originalFee = MoneyUtils.add(originalFee, new BigDecimal(fundTradeRatio.getOrigin_fee()));
                    discountFee = MoneyUtils.add(discountFee, new BigDecimal(fundTradeRatio.getDiscount_fee()));
                } else {
                    log.warn("fund rate is null");

                    if (!Strings.isNullOrEmpty(fundTradeRatio.getFund_code())) {
                        PortfolioSingleFundRate singleFundRate = new PortfolioSingleFundRate();
                        singleFundRate.setFundCode(fundTradeRatio.getFund_code());
                        singleFundRate.setFee("0");
                        singleFundRate.setOriginFee("0");
                        singleFundRate.setDiscountFee("0");
                        singleFundRates.add(singleFundRate);
                    }
                }

            }
        } catch (Exception e) {
            log.error("fail to get news Model", e);
        } finally {
            executorService.shutdown();
        }

        Map<String, PortfolioSingleFundRate> map = new HashMap<>();
        singleFundRates.forEach(item -> {
            map.put(item.getFundCode(), item);
        });

        List<PortfolioSingleFundRate> sortFundRates = new ArrayList<>(fundCodesLength);

        for (int i = 0; i < fundCodesArray.length; i++) {
            sortFundRates.add(map.get(fundCodesArray[i]));
            i++;
        }

        PortfolioRate rate = new PortfolioRate();
        rate.setFee(MoneyUtils.formatMoney(finalFee, 2));
        rate.setOriginFee(MoneyUtils.formatMoney(originalFee, 2));
        rate.setDiscountFee(MoneyUtils.formatMoney(discountFee, 2));
        rate.setPortfolio(sortFundRates);

        return rate;
    }

    @Override
    public BaseResult getTradeDetail(Long userId, String portfolioCode) {
        BaseResult result = new BaseResult(false);

        List<PortfolioQryModel> portfolioQryModels = fundCombinationService.queryPorfolio(portfolioCode);

        if (ListUtil.isEmpty(portfolioQryModels)) {
            result.setErrorCode(ErrorConst.PORTFOLIO_IS_NOT_EXIST);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConst.PORTFOLIO_IS_NOT_EXIST, portfolioCode));
            return result;
        }

        int fundCodesLength = portfolioQryModels.size();

        //线程池
        ExecutorService executorService = Executors.newFixedThreadPool(Math.min(fundCodesLength, MAX_THREAD_COUNT));
        //实例化CompletionService
        final CompletionService<FundModel> completionService = new ExecutorCompletionService<>(executorService);

        String[] fundCodes = new String[fundCodesLength];
        for (int i = 0; i < fundCodesLength; i++) {
            PortfolioQryModel portfolioQryModel = portfolioQryModels.get(i);

            String fundCode = portfolioQryModel.getFundCode();
            fundCodes[i] = fundCode;

            completionService.submit(() -> {
                FundModel model = queryService.queryFund(fundCode);

                log.info("query fund info:[{}]", model);

                return model;
            });
        }

        boolean finalCanBuy = true, finalCanSell = true;
        try {
            for (int i = 0; i < fundCodesLength; i++) {
                Future<FundModel> f = completionService.take();

                FundModel fundModel = f.get();

                if (fundModel != null) {

                    finalCanBuy = finalCanBuy && fundModel.getCanBuy();
                    finalCanSell = finalCanSell && fundModel.getCanSell();

                } else {
                    log.warn("fund model is null");
                }

            }
        } catch (Exception e) {
            log.error("fail to get FundModel", e);
        } finally {
            executorService.shutdown();
        }

        PortfolioTradeDetailModel model = new PortfolioTradeDetailModel();
        model.setPortfolioCode(portfolioCode);
        model.setCanBuy(finalCanBuy);
        model.setCanSell(finalCanSell);

        User user = userService.getUser(userId);
        if (user != null && user.getIsOrg()) {
            model.setCanBuy(false);
            model.setCanSell(false);
        }

        NewsModel newsModel = getNewsModel(fundCodes);
        if (newsModel == null) {
            log.error("组合的风险等级为空，请检查");
        } else {
            model.setRiskLevel(newsModel.getRiskLevel());
            model.setRiskLevelStr(newsModel.getRiskLevelString());
        }

        model.setPortfolio(portfolioQryModels);

        // 批量获取基金名称
        List<SecurityProduct> products = securityProductService.querySecurityProducts(Arrays.asList(fundCodes));
        for (PortfolioQryModel temp : portfolioQryModels) {
            for (SecurityProduct product : products) {
                if (EqualsUtil.equals(temp.getFundCode(), product.getSecuCode())) {
                    temp.setFundName(product.getChiNameAbbr());
                    break;
                }
            }
        }

        result.setSuccess(true);
        result.setResult(model);
        return result;
    }

    @Override
    public BigDecimal queryAssertByNewTradeAcco(String newTradeAcco, String portfolioCode) {

        CombSharesQryDto dto = CombSharesQryDto.builder()
                                               .trade_acco(newTradeAcco)
                                               .portfolio_code(portfolioCode)
                                               .build();

        Map<String, Object> map = hsCombSharesQryService.query(dto);

        List<Map<String, Object>> list = new ArrayList<>();

        BaseResult result = new BaseResult();


        HundsunUtils.ConvertToList(map, list, result);
        if (!result.getSuccess()) {
            log.error("查询组合份额失败");
            return BigDecimal.ZERO;
        }
        List<CombSharesQryModel> combSharesQryModels = ModelPopulateUtils.populateCombSharesQry(list);

        if (ListUtil.isNotEmpty(combSharesQryModels)) {

            BigDecimal sum = new BigDecimal(0);

            for (CombSharesQryModel item : combSharesQryModels) {
                sum = sum.add(item.getEnableShares());
            }
            return sum;
        }


        // 遍历计算
        return BigDecimal.ZERO;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateTradeStatusSuc(TradePortfolioStatusVo statusVo) {

        TradeOrder tradeOrderRecord = new TradeOrder();
        tradeOrderRecord.setOrderId(statusVo.getTradeOrderId());
        tradeOrderRecord.setOrderStatus(GlobalConstant.ORDER_STATUS_CONFIRM);

        if (statusVo.getConfirmDate() != null) {
            tradeOrderRecord.setConfirmDate(statusVo.getConfirmDate());
        }

        tradeOrderDao.updateStatus(tradeOrderRecord);


        PortfolioFundOrder portfolioFundOrder = new PortfolioFundOrder();
        portfolioFundOrder.setId(statusVo.getPortfolioFundOrderId())
                          .setStatus(GlobalConstant.FUND_STATUS_CONFIRM_SUC);

        portfolioFundOrderDao.updateByPrimaryKeySelective(portfolioFundOrder);

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateTradeStatusFail(TradePortfolioStatusVo statusVo) {

        TradeOrder tradeOrderRecord = new TradeOrder();
        tradeOrderRecord.setOrderId(statusVo.getTradeOrderId());
        tradeOrderRecord.setOrderStatus(GlobalConstant.ORDER_STATUS_CONFIRM_FAIL);

        if (statusVo.getConfirmDate() != null) {
            tradeOrderRecord.setConfirmDate(statusVo.getConfirmDate());
        }

        tradeOrderDao.updateStatus(tradeOrderRecord);


        PortfolioFundOrder portfolioFundOrder = new PortfolioFundOrder();
        portfolioFundOrder.setId(statusVo.getPortfolioFundOrderId())
                          .setStatus(GlobalConstant.FUND_STATUS_APPLY_FAIL);

        portfolioFundOrderDao.updateByPrimaryKeySelective(portfolioFundOrder);
    }

    @Override
    public Map<String, List<ShareDetailTotalModel>> getShareMap(List<String> portfolioCode, Map<String, List<ShareDetailQryModel>> sharePortfolioMap, Map<String, List<PortfolioQryModel>> portfolioMap) {
        Map<String, List<ShareDetailTotalModel>> shareTotalMap = new HashMap<>();
        List<ShareDetailTotalModel> shareDetailTotalModels = new ArrayList<>();
        List<String> fundCode = new ArrayList<>();
        for (String code : portfolioCode) {
            shareDetailTotalModels.clear();
            if (sharePortfolioMap.get(code).size() == 0) {
                continue;
            }
            fundCode.clear();
            for (ShareDetailQryModel shareDetailQryModel : sharePortfolioMap.get(code)) {
                if (!fundCode.contains(shareDetailQryModel.getFundCode())) {
                    fundCode.add(shareDetailQryModel.getFundCode());
                }
            }
            for (String fc : fundCode) {
                //基金名称相同的加到一个model里去
                ShareDetailTotalModel shareDetailTotalModel = new ShareDetailTotalModel();
                BigDecimal enableShares = null;
                BigDecimal businessFrozenAmount = null;
                BigDecimal unpaidIncome = null;
                for (ShareDetailQryModel shareDetailQryModel : sharePortfolioMap.get(code)) {
                    //如果基金名称相同
                    if (fc.equals(shareDetailQryModel.getFundCode())) {
                        enableShares = MoneyUtils.add(enableShares, shareDetailQryModel.getEnableShares());
                        businessFrozenAmount = MoneyUtils.add(businessFrozenAmount, shareDetailQryModel.getBusinessFrozenAmount());
                        unpaidIncome = MoneyUtils.add(unpaidIncome, shareDetailQryModel.getUnpaidIncome());
                    }
                }
                shareDetailTotalModel.setBusinessFrozenAmount(businessFrozenAmount);
                shareDetailTotalModel.setEnableShares(enableShares);
                shareDetailTotalModel.setFundCode(fc);
                shareDetailTotalModel.setPortfolioCode(code);
                shareDetailTotalModel.setUnpaidIncome(unpaidIncome);
                shareDetailTotalModel.setProtocolName(portfolioMap.get(code).get(0).getProtocolName());
                shareDetailTotalModels.add(shareDetailTotalModel);
            }
            shareTotalMap.put(code, shareDetailTotalModels);
        }
        return shareTotalMap;
    }

    @Override
    public List<ShareDetailTotalModel> getShareWithoutPortfolio(List<ShareDetailQryModel> shareDetailQryModels) {
        List<String> fundCode = new ArrayList<>();
        List<ShareDetailTotalModel> shareDetailTotalModels1 = new ArrayList<>();
        for (ShareDetailQryModel shareDetailQryModel : shareDetailQryModels) {
            if (!fundCode.contains(shareDetailQryModel.getFundCode())) {
                fundCode.add(shareDetailQryModel.getFundCode());
            }
        }
        for (String fc : fundCode) {
            //基金名称相同的加到一个model里去
            ShareDetailTotalModel shareDetailTotalModel = new ShareDetailTotalModel();
            BigDecimal enableShares = null;
            BigDecimal businessFrozenAmount = null;
            BigDecimal unpaidIncome = null;
            for (ShareDetailQryModel shareDetailQryModel : shareDetailQryModels) {
                //如果基金名称相同
                if (fc.equals(shareDetailQryModel.getFundCode())) {
                    enableShares = MoneyUtils.add(enableShares, shareDetailQryModel.getEnableShares());
                    businessFrozenAmount = MoneyUtils.add(businessFrozenAmount, shareDetailQryModel.getBusinessFrozenAmount());
                    unpaidIncome = MoneyUtils.add(unpaidIncome, shareDetailQryModel.getUnpaidIncome());
                }
            }
            shareDetailTotalModel.setBusinessFrozenAmount(businessFrozenAmount);
            shareDetailTotalModel.setEnableShares(enableShares);
            shareDetailTotalModel.setFundCode(fc);
            shareDetailTotalModel.setUnpaidIncome(unpaidIncome);
            shareDetailTotalModels1.add(shareDetailTotalModel);
        }
        return shareDetailTotalModels1;
    }

    @Override
    public void getPortfolioMap(List<PortfolioQryModel> portfolioQryModels, Map<String, List<PortfolioQryModel>> portfolioMap, List<String> portfolioCode) {
        for (PortfolioQryModel portfolioQryModel : portfolioQryModels) {
            if (!portfolioMap.containsKey(portfolioQryModel.getPortfolioCode())) {
                List<PortfolioQryModel> list2 = new ArrayList<>();
                portfolioMap.put(portfolioQryModel.getPortfolioCode(), list2);
            }
            portfolioMap.get(portfolioQryModel.getPortfolioCode()).add(portfolioQryModel);
            if (!portfolioCode.contains(portfolioQryModel.getPortfolioCode()) && !Strings.isNullOrEmpty(portfolioQryModel.getPortfolioCode())) {
                portfolioCode.add(portfolioQryModel.getPortfolioCode());
            }
        }
    }

    @Override
    public void getCombMap(List<CombSharesQryModel> combSharesQryModels, Map<String, List<CombSharesQryModel>> combMap, List<String> portfolioCode) {
        int k;
        for (CombSharesQryModel combSharesQryModel : combSharesQryModels) {
            //combSharesQryModel.setTradeAcco("");
            k = 0;
            if (!combMap.containsKey(combSharesQryModel.getPortfolioCode())) {
                List<CombSharesQryModel> list = new ArrayList<>();
                combMap.put(combSharesQryModel.getPortfolioCode(), list);
            }
            if (combMap.get(combSharesQryModel.getPortfolioCode()).size() != 0) {
                for (CombSharesQryModel combSharesQryModelMap : combMap.get(combSharesQryModel.getPortfolioCode())) {
                    if (combSharesQryModelMap.getPortfolioCode().equals(combSharesQryModel.getPortfolioCode()) && combSharesQryModelMap.getFundCode().equals(combSharesQryModel.getFundCode())) {
                        combSharesQryModelMap.setFundShare(MoneyUtils.add(combSharesQryModel.getFundShare(), combSharesQryModelMap.getFundShare()));
                        combSharesQryModelMap.setEnableShares(MoneyUtils.add(combSharesQryModel.getEnableShares(), combSharesQryModelMap.getEnableShares()));
                        combSharesQryModelMap.setFrozenShares(MoneyUtils.add(combSharesQryModel.getFrozenShares(), combSharesQryModelMap.getFrozenShares()));
                        k = 1;
                    }
                }
            }
            if (k == 0) {
                combMap.get(combSharesQryModel.getPortfolioCode()).add(combSharesQryModel);
            }
            if (!portfolioCode.contains(combSharesQryModel.getPortfolioCode())) {
                portfolioCode.add(combSharesQryModel.getPortfolioCode());
            }
        }
    }

    @Override
    public void getCombTradeMap(List<CombSharesQryModel> combSharesQryModels, Map<String, List<CombSharesQryModel>> combTradeMap) {
        for (CombSharesQryModel combSharesQryModel : combSharesQryModels) {
            if (!combTradeMap.containsKey(combSharesQryModel.getTradeAcco())) {
                List<CombSharesQryModel> list = new ArrayList<>();
                combTradeMap.put(combSharesQryModel.getTradeAcco(), list);
            }
            combTradeMap.get(combSharesQryModel.getTradeAcco()).add(combSharesQryModel);
        }
    }

    @Override
    public void getCombMapByPortfolioCode(List<CombSharesQryModel> combSharesQryModels, Map<String, List<CombSharesQryModel>> combMap, List<String> portfolioCodes, String portfolioCode) {
        int k;
        for (CombSharesQryModel combSharesQryModel : combSharesQryModels) {
            if (!portfolioCode.equals(combSharesQryModel.getPortfolioCode())) {
                continue;
            }
            //combSharesQryModel.setTradeAcco("");
            k = 0;
            if (!combMap.containsKey(combSharesQryModel.getPortfolioCode())) {
                List<CombSharesQryModel> list = new ArrayList<>();
                combMap.put(combSharesQryModel.getPortfolioCode(), list);
            }
            if (combMap.get(combSharesQryModel.getPortfolioCode()).size() != 0) {
                for (CombSharesQryModel combSharesQryModelMap : combMap.get(combSharesQryModel.getPortfolioCode())) {
                    if (combSharesQryModelMap.getPortfolioCode().equals(combSharesQryModel.getPortfolioCode()) && combSharesQryModelMap.getFundCode().equals(combSharesQryModel.getFundCode())) {
                        combSharesQryModelMap.setFundShare(MoneyUtils.add(combSharesQryModel.getFundShare(), combSharesQryModelMap.getFundShare()));
                        combSharesQryModelMap.setEnableShares(MoneyUtils.add(combSharesQryModel.getEnableShares(), combSharesQryModelMap.getEnableShares()));
                        combSharesQryModelMap.setFrozenShares(MoneyUtils.add(combSharesQryModel.getFrozenShares(), combSharesQryModelMap.getFrozenShares()));
                        k = 1;
                    }
                }
            }
            if (k == 0) {
                combMap.get(combSharesQryModel.getPortfolioCode()).add(combSharesQryModel);
            }
            if (!portfolioCode.contains(combSharesQryModel.getPortfolioCode())) {
                portfolioCodes.add(combSharesQryModel.getPortfolioCode());
            }
        }
    }

    @Override
    public void getCombTradeMapByPortfolioCode(List<CombSharesQryModel> combSharesQryModels, Map<String, List<CombSharesQryModel>> combTradeMap, String portfolioCode) {
        for (CombSharesQryModel combSharesQryModel : combSharesQryModels) {
            if (!portfolioCode.equals(combSharesQryModel.getPortfolioCode())) {
                continue;
            }
            if (!combTradeMap.containsKey(combSharesQryModel.getTradeAcco())) {
                List<CombSharesQryModel> list = new ArrayList<>();
                combTradeMap.put(combSharesQryModel.getTradeAcco(), list);
            }
            combTradeMap.get(combSharesQryModel.getTradeAcco()).add(combSharesQryModel);
        }
    }

    @Override
    public void getShareMap(List<ShareModel> shareModels, Map<String, ShareModel> shareMap) {
        for (ShareModel shareModel : shareModels) {
            if (!shareMap.containsKey(shareModel.getFundCode())) {
                shareMap.put(shareModel.getFundCode(), shareModel);
            } else {
                shareMap.get(shareModel.getFundCode()).setCurrentShare(MoneyUtils.add(shareMap.get(shareModel.getFundCode()).getCurrentShare(), shareModel.getCurrentShare()));
                shareMap.get(shareModel.getFundCode()).setEnableShares(MoneyUtils.add(shareMap.get(shareModel.getFundCode()).getEnableShares(), shareModel.getEnableShares()));
                shareMap.get(shareModel.getFundCode()).setFrozenShare(MoneyUtils.add(shareMap.get(shareModel.getFundCode()).getFrozenShare(), shareModel.getFrozenShare()));
                shareMap.get(shareModel.getFundCode()).setUnpaidIncome(MoneyUtils.add(shareMap.get(shareModel.getFundCode()).getUnpaidIncome(), shareModel.getUnpaidIncome()));
            }
        }
    }


    private void storeData(Map<String, Object> map, String capitalMode, String bankCode, String bankAccount,
                           BigDecimal orderBalance, String orderSn, Long userId, String channelId,
                           String tradeType, Integer orderStatus, Date applyDate, String externalApplyNo,
                           String portfolioCode, String commnuicContent) {

        if (StringUtils.isBlank(externalApplyNo)){
            return;
        }

        // 插入数据到订单表
        TradeOrder tradeOrder = new TradeOrder();
        tradeOrder.setMoneyType(GlobalConstant.MONEY_TYPE_RMB);
        tradeOrder.setPaymentType(capitalMode);
        tradeOrder.setBankCode(bankCode);
        tradeOrder.setBankAccount(bankAccount);
        tradeOrder.setOrderBalance(orderBalance);
        tradeOrder.setOrderSn(orderSn);
        tradeOrder.setUserId(userId);
        tradeOrder.setChannelId(channelId);
        tradeOrder.setTradeType(tradeType);
        tradeOrder.setOrderStatus(orderStatus);
        tradeOrder.setApplyDate(applyDate);
        tradeOrder.setBizGroup(GlobalConstant.BIZ_GROUP_PORTFOLIO);

        // 插入数据到订单表
        tradeOrderDao.add(tradeOrder);


        // 插入到组合记录表
        PortfolioFundOrder record = new PortfolioFundOrder();
        record.setOrderId(tradeOrder.getOrderId());
        record.setUserId(userId);

        Integer portfolioStatus = EqualsUtil.equals(GlobalConstant.ORDER_STATUS_APPLY, orderStatus) ?
            GlobalConstant.FUND_STATUS_AWAITING_CONFIRM : GlobalConstant.FUND_STATUS_APPLY_FAIL;

        record.setStatus(portfolioStatus);
        record.setInvestFlag(Integer.valueOf(HsConst.INVEST_FLAG_PLAN));
        record.setPortfolioCode(portfolioCode);
        record.setBalance(orderBalance);
        record.setCommunicContent(commnuicContent);
        record.setExternalApplyNo(externalApplyNo);
        record.setCreateDate(new Date());

        portfolioFundOrderDao.insertSelective(record);

        // 插入交易记录流水
        Transaction transaction = ObjectCreator.createTransaction(map);
        transaction.setOrderId(tradeOrder.getOrderId());
        // 不管成功或失败，都插入交易流水
        transactionDao.add(transaction);
    }


}
